/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 d3leaf@126.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.mapperdb.db;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.session.SqlSession;

import com.mapperdb.core.Assert;
import com.mapperdb.db.pro.DeletePro;
import com.mapperdb.db.pro.InsertPro;
import com.mapperdb.db.pro.SelectPro;
import com.mapperdb.db.pro.UpdatePro;

@SuppressWarnings("unchecked")
public class DbPro<T> {
	private static final Map<Class<?>, DbPro<?>> map = new HashMap<Class<?>, DbPro<?>>();
	private final Config config;
	private Class<T> entityClass;

	// --------------------------------------------------------
	// c
	// --------------------------------------------------------
	public DbPro(Config config, Class<T> entityClass) {
		Assert.notNull(entityClass, "entityClass can not be null");
		Assert.notNull(config, "Config can not be null");
		this.config = config;
		this.entityClass = entityClass;
	}

	// --------------------------------------------------------
	// use SqlSession
	// --------------------------------------------------------
	public static <T> DbPro<T> use(SqlSession sqlSession, Class<T> entityClass) {
		Config config = DbKit.getConfig(sqlSession.getConfiguration());
		Assert.notNull(entityClass, "entityClass can not be null");
		Assert.notNull(config, "The SqlSession [" + sqlSession + "] not initialize ActiveRecord");
		return use(Config.newConfig(config, sqlSession), entityClass);
	}

	// --------------------------------------------------------
	// use Config
	// --------------------------------------------------------
	public static <T> DbPro<T> use(String configName, Class<T> entityClass) {
		Config config = DbKit.getConfig(configName);
		Assert.notNull(config, "Config not found by configName: " + configName);
		Assert.notNull(entityClass, "entityClass can not be null");
		return use(config, entityClass);
	}

	public static <T> DbPro<T> use(Config config, Class<T> entityClass) {
		Assert.notNull(config, "Config can not be null");
		Assert.notNull(entityClass, "entityClass can not be null");

		DbPro<T> result = (DbPro<T>) map.get(entityClass);
		if (result == null) {
			synchronized (map) {
				if (result == null) {
					map.put(entityClass, result = new DbPro<T>(config, entityClass));
				}
			}
		}
		return result;
	}

	public static <T> DbPro<T> use(Class<T> entityClass) {
		return use(DbKit.getConfig().getName(), entityClass);
	}

	// --------------------------------------------------------
	// select
	// --------------------------------------------------------
	public T selectByPrimaryKey(@Param("id") Serializable idVal) {
		String id = new SelectPro(config, entityClass).types(Serializable.class).selectByPrimaryKey();
		return config.getSqlSession().selectOne(id, idVal);
	}

	public T selectByPrimaryKeys(T record) {
		String id = new SelectPro(config, entityClass).types(entityClass).selectByPrimaryKeys();
		return config.getSqlSession().selectOne(id, record);
	}

	public T selectOneByWhereSql(@Param("sql") String sql, @Param("param") Object param) {
		String id = new SelectPro(config, entityClass).types(String.class, Object.class).selectOneByWhereSql();
		ParamMap<Object> parameter = new ParamMap<Object>();
		parameter.put("sql", sql);
		parameter.put("param", param);
		return config.getSqlSession().selectOne(id, parameter);
	}

	public List<T> selectListByWhereSql(@Param("sql") String sql, @Param("param") Object param) {
		String id = new SelectPro(config, entityClass).types(String.class, Object.class).selectListByWhereSql();
		ParamMap<Object> parameter = new ParamMap<Object>();
		parameter.put("sql", sql);
		parameter.put("param", param);
		return config.getSqlSession().selectList(id, parameter);
	}

	public List<T> selectList(T record) {
		String id = new SelectPro(config, entityClass).types(entityClass).selectList();
		return config.getSqlSession().selectList(id, record);
	}

	public T selectOne(T record) {
		String id = new SelectPro(config, entityClass).types(entityClass).selectOne();
		return config.getSqlSession().selectOne(id, record);
	}

	public int selectCount() {
		String id = new SelectPro(config, entityClass, int.class).types().selectCount();
		return config.getSqlSession().selectOne(id);
	}

	public int selectCountByWhereSql(@Param("sql") String sql, @Param("param") Object param) {
		String id = new SelectPro(config, entityClass, int.class).types(String.class, Object.class).selectCountByWhereSql();
		ParamMap<Object> parameter = new ParamMap<Object>();
		parameter.put("sql", sql);
		parameter.put("param", param);
		return config.getSqlSession().selectOne(id, parameter);
	}

	// --------------------------------------------------------
	// update
	// --------------------------------------------------------
	public int updateByPrimaryKey(T record) {
		String id = new UpdatePro(config, entityClass)//
				.types(entityClass)//
				.updateByPrimaryKey();
		return config.getSqlSession().update(id, record);
	}

	public int updateByPrimaryKeySelective(T record) {
		String id = new UpdatePro(config, entityClass)//
				.types(entityClass)//
				.updateByPrimaryKeySelective();
		return config.getSqlSession().update(id, record);
	}

	public int updateByWhereSql(@Param("record") T record, @Param("sql") String sql, @Param("param") Object param) {
		String id = new UpdatePro(config, entityClass)//
				.types(entityClass, String.class, Object.class)//
				.updateByWhereSql();
		ParamMap<Object> parameter = new ParamMap<Object>();
		parameter.put("sql", sql);
		parameter.put("param", param);
		parameter.put("record", record);
		return config.getSqlSession().update(id, parameter);
	}

	public int updateByWhereSqlSelective(@Param("record") T record, @Param("sql") String sql, @Param("param") Object param) {
		String id = new UpdatePro(config, entityClass)//
				.types(entityClass, String.class, Object.class)//
				.updateByWhereSqlSelective();
		ParamMap<Object> parameter = new ParamMap<Object>();
		parameter.put("sql", sql);
		parameter.put("param", param);
		parameter.put("record", record);
		return config.getSqlSession().update(id, parameter);
	}

	// --------------------------------------------------------
	// insert
	// --------------------------------------------------------
	public int insertSelective(T record) {
		String id = new InsertPro(config, entityClass).types(entityClass).insertSelective();
		return config.getSqlSession().insert(id, record);
	}

	public int insert(T record) {
		String id = new InsertPro(config, entityClass).types(entityClass).insert();
		return config.getSqlSession().insert(id, record);
	}

	// --------------------------------------------------------
	// delete
	// --------------------------------------------------------
	public int delete(T record) {
		String id = new DeletePro(config, entityClass).types(entityClass).delete();
		return config.getSqlSession().delete(id, record);
	}

	public int deleteByPrimaryKeys(T record) {
		String id = new DeletePro(config, entityClass).types(entityClass).deleteByPrimaryKeys();
		return config.getSqlSession().delete(id, record);
	}

	public int deleteByPrimaryKey(@Param("id") Serializable key) {
		String id = new DeletePro(config, entityClass).types(Serializable.class).deleteByPrimaryKey();
		return config.getSqlSession().delete(id, key);
	}

	public int deleteByWhereSql(@Param("sql") String sql, @Param("param") Object param) {
		String id = new DeletePro(config, entityClass).types(String.class, Object.class).deleteByWhereSql();
		ParamMap<Object> parameter = new ParamMap<Object>();
		parameter.put("sql", sql);
		parameter.put("param", param);
		return config.getSqlSession().delete(id, parameter);
	}
}
